using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using Microsoft.Ink;
using Microsoft.StylusInput;
using Microsoft.StylusInput.PluginData;
using System.Drawing.Drawing2D;

namespace Zooming
{
    public partial class Canvas : UserControl, IStylusSyncPlugin
    {
        public Canvas()
        {
            // initialize the current zoom
            this.mCurrentZoom = 1.0f;

            // initialize strokes and pens
            this.mStrokes = new List<Stroke>();
            this.mZoomPen = new Pen(Color.Black);
            this.mCurrentPen = new Pen(Color.Black, 100.0f);
            this.mZoomPen.SetLineCap(LineCap.Round, LineCap.Round, DashCap.Round);
            this.mCurrentPen.SetLineCap(LineCap.Round, LineCap.Round, DashCap.Round);

            // initialize components
            InitializeComponent();

            // initialize stylus
            this.mStylus = new RealTimeStylus(this.mPanel);
            this.mStylus.SyncPluginCollection.Add(this);
            this.mStylus.Enabled = true;
            
            // subscribe to the paint event
            this.mPanel.Paint += this.panel_OnPaint;
        }

        Graphics InitializeGraphics(Graphics pGraphics)
        {
            pGraphics.PageScale = 0.01f * this.mCurrentZoom;
            pGraphics.PageUnit = GraphicsUnit.Millimeter;
            pGraphics.SmoothingMode = SmoothingMode.AntiAlias;
            
            return pGraphics;
        }

        void panel_OnPaint(object sender, PaintEventArgs e)
        {
            Graphics graphics = InitializeGraphics(e.Graphics);
            graphics.Clear(Color.White);
            foreach (Stroke stroke in this.mStrokes)
            {
                stroke.Draw(graphics, this.mCurrentZoom);
            }
        }

        public float Zoom
        {
            get
            {
                return this.mCurrentZoom;
            }
            set
            {
                this.mCurrentZoom = Math.Max(0.10f, Math.Min(4.00f, value));
                this.RecenterPanel();
            }
        }

        private void RecenterPanel()
        {
            Size panelSize = new Size((int)(Variables.Instance.CanvasSize.Width * this.mCurrentZoom), (int)(Variables.Instance.CanvasSize.Height * this.mCurrentZoom));

            // set the size of the panel
            this.mPanel.Size = panelSize;

            // set the position of the panel
            this.mPanel.Left = (int)Math.Max(5.0f, this.Width / 2.0f - panelSize.Width / 2.0f);
            this.mPanel.Top = (int)Math.Max(5.0f, this.Height / 2.0f - panelSize.Height / 2.0f);

            // set the actual canvas size
            Variables.Instance.ActualCanvasSize = panelSize;

            // resize the zoom pen
            this.mZoomPen.Width = this.mCurrentPen.Width * this.mCurrentZoom;// *this.mCurrentZoom;

            // refresh the panel
            this.mPanel.Refresh();
        }

        protected override void OnResize(EventArgs e)
        {
            this.RecenterPanel();
        }

        #region IStylusAsyncPlugin Members

        public DataInterestMask DataInterest
        {
            get { return DataInterestMask.Packets | DataInterestMask.StylusDown | DataInterestMask.StylusUp; }
        }

        public void Packets(RealTimeStylus sender, PacketsData data)
        {
            for (int i = 0; i < data.Count; i += data.PacketPropertyCount)
            {
                Point point = new Point(data[i], data[i + 1]);
                this.mGraphics.DrawLine(this.mZoomPen, this.mLastPoint, point);
                this.mLastPoint = point;
                this.mPoints.Add(point);
            }
        }

        public void StylusDown(RealTimeStylus sender, StylusDownData data)
        {
            this.mPoints = new List<PointF>();
            this.mLastPoint = new Point(data[0], data[1]);
            this.mGraphics = this.mPanel.CreateGraphics();
            this.mGraphics.SmoothingMode = SmoothingMode.AntiAlias;
            this.mGraphics.PageUnit = GraphicsUnit.Millimeter;
            this.mGraphics.PageScale = 0.01f;
            this.mPoints.Add(this.mLastPoint);
        }

        public void StylusUp(RealTimeStylus sender, StylusUpData data)
        {
            Point point = new Point(data[0], data[1]);
            this.mGraphics.DrawLine(this.mZoomPen, this.mLastPoint.X + 0.001f, this.mLastPoint.Y + 0.001f, point.X + 0.0f, point.Y + 0.0f);
            this.mPoints.Add(point);
            this.mStrokes.Add(new Stroke(ConvertPoints(this.mPoints.ToArray()), this.mCurrentPen));
            this.mGraphics.Dispose();
        }

        private PointF[] ConvertPoints(PointF[] points)
        {
            for (int i = 0; i < points.Length; i++)
            {
                points[i].X = (points[i].X / this.mCurrentZoom);
                points[i].Y = (points[i].Y / this.mCurrentZoom);
            }
            return points;
        }


        #region UNUSED STYLUS EVENTS
        public void CustomStylusDataAdded(RealTimeStylus sender, CustomStylusData data)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void Error(RealTimeStylus sender, ErrorData data)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void InAirPackets(RealTimeStylus sender, InAirPacketsData data)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void RealTimeStylusDisabled(RealTimeStylus sender, RealTimeStylusDisabledData data)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void RealTimeStylusEnabled(RealTimeStylus sender, RealTimeStylusEnabledData data)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void StylusButtonDown(RealTimeStylus sender, StylusButtonDownData data)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void StylusButtonUp(RealTimeStylus sender, StylusButtonUpData data)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void StylusInRange(RealTimeStylus sender, StylusInRangeData data)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void StylusOutOfRange(RealTimeStylus sender, StylusOutOfRangeData data)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void SystemGesture(RealTimeStylus sender, SystemGestureData data)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void TabletAdded(RealTimeStylus sender, TabletAddedData data)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void TabletRemoved(RealTimeStylus sender, TabletRemovedData data)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        #endregion

        #endregion

        private RealTimeStylus mStylus;
        private Point mLastPoint;
        private Graphics mGraphics;
        private Pen mCurrentPen;
        private Pen mZoomPen;
        private float mCurrentZoom;
        private List<PointF> mPoints;
        private List<Stroke> mStrokes;
    }
}
